package mybatis.builder.xml;

import com.alibaba.druid.pool.DruidDataSource;
import mybatis.exception.MyBatisException;
import mybatis.io.Resources;
import mybatis.session.Configuration;
import mybatis.type.TypeHandler;
import org.apache.commons.lang3.StringUtils;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.Node;
import org.dom4j.io.SAXReader;
import utils.LogUtils;

import java.io.InputStream;
import java.util.List;
import java.util.Objects;
import java.util.Properties;

import static com.alibaba.druid.pool.DruidAbstractDataSource.*;

/**
 * XML主配置文件构造器类
 *
 * @author richie696
 * @version 1.0
 * @since 2020/11/08
 */
public class XmlConfigBuilder {

    // 类型处理器节点名称
    private static final String TYPE_HANDLER = "typeHandler";
    // 类型别名节点名称
    private static final String TYPE_ALIAS = "typeAlias";
    // 数据源配置信息节点名称
    private static final String PROPERTIES = "property";
    // 主配置文件
    private final Configuration configuration;

    public XmlConfigBuilder() {
        configuration = new Configuration();
    }

    public XmlConfigBuilder(Configuration configuration) {
        this.configuration = configuration;
    }

    /**
     * MyBatis主配置文件解析方法
     *
     * @param inputStream 配置文件的输入流
     * @return 返回解析后的主配置文件对象
     * @throws DocumentException 配置文件解析出错时抛出该异常
     */
    public Configuration parse(InputStream inputStream) throws DocumentException {
        // 解析XML文件信息对象
        Document document = new SAXReader().read(inputStream);
        Element rootElement = document.getRootElement();
        // 解析DataSource节点
        parseDataSourceConfig(rootElement);
        // 解析TypeAliases节点
        parseTypeAliases(rootElement);
        // 解析TypeHandlers节点
        parseTypeHandlers(rootElement);
        // 解析Mappers节点
        parseMappersConfig(rootElement);
        // 返回主配置对象信息
        return configuration;
    }

    /**
     * 解析数据源配置节点
     *
     * @param rootElement 主配置文件的根节点元素
     */
    private void parseDataSourceConfig(Element rootElement) {
        // 查找数据源相关配置
        List<Node> dataSourceNodes = rootElement.selectNodes("//" + PROPERTIES);
        if (dataSourceNodes.isEmpty()) {
            return;
        }
        Properties properties = new Properties();
        // 遍历数据源节点信息
        dataSourceNodes.forEach(node -> {
            if (node == null || !PROPERTIES.equals(node.getName())) {
                return;
            }
            String name = ((Element) node).attributeValue("name");
            String value = ((Element) node).attributeValue("value");
            properties.setProperty(name, value);
        });
        initialDataSource(properties);
    }

    /**
     * 初始化数据源的方法
     *
     * @param properties 数据源属性对象
     */
    private void initialDataSource(Properties properties) {
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setDriverClassName(properties.getProperty("driverClass"));
        dataSource.setUrl(properties.getProperty("jdbcUrl"));
        dataSource.setUsername(properties.getProperty("username"));
        dataSource.setPassword(properties.getProperty("password"));
        String value = properties.getProperty("initialSize");
        if (properties.containsKey("initialSize")) {
            dataSource.setInitialSize(StringUtils.isNumeric(value) ? Integer.parseInt(value) : DEFAULT_INITIAL_SIZE);
        }
        if (properties.containsKey("maxActive")) {
            value = properties.getProperty("maxActive");
            dataSource.setMaxActive(StringUtils.isNumeric(value) ? Integer.parseInt(value) : DEFAULT_MAX_ACTIVE_SIZE);
        }
        if (properties.containsKey("minIdle")) {
            value = properties.getProperty("minIdle");
            dataSource.setMinIdle(StringUtils.isNumeric(value) ? Integer.parseInt(value) : DEFAULT_MIN_IDLE);
        }
        if (properties.containsKey("maxWait")) {
            value = properties.getProperty("maxWait");
            dataSource.setMinIdle(StringUtils.isNumeric(value) ? Integer.parseInt(value) : DEFAULT_MAX_WAIT);
        }
        if (properties.containsKey("name")) {
            value = properties.getProperty("name");
            dataSource.setName(StringUtils.isNotBlank(value) ? value : dataSource.getName());
        }
        if (properties.containsKey("poolPreparedStatements")) {
            value = properties.getProperty("poolPreparedStatements");
            dataSource.setPoolPreparedStatements(Boolean.parseBoolean(value));
        }
        if (properties.containsKey("maxOpenPreparedStatements")) {
            value = properties.getProperty("maxOpenPreparedStatements");
            if (StringUtils.isNumeric(value)) {
                dataSource.setMaxOpenPreparedStatements(Integer.parseInt(value));
            }
        }
        if (properties.containsKey("validationQuery")) {
            value = properties.getProperty("validationQuery");
            if (StringUtils.isNotBlank(value)) {
                dataSource.setValidationQuery(value);
            }
        }
        if (properties.containsKey("testOnBorrow")) {
            value = properties.getProperty("testOnBorrow");
            dataSource.setTestOnBorrow(Boolean.parseBoolean(value));
        }
        if (properties.containsKey("testOnReturn")) {
            value = properties.getProperty("testOnReturn");
            dataSource.setTestOnReturn(Boolean.parseBoolean(value));
        }
        if (properties.containsKey("testWhileIdle")) {
            value = properties.getProperty("testWhileIdle");
            dataSource.setTestWhileIdle(Boolean.parseBoolean(value));
        }
        configuration.setDataSource(dataSource);
    }

    /**
     * 解析类型别名节点的方法
     *
     * @param rootElement 配置文件根节点对象
     */
    private void parseTypeAliases(Element rootElement) {
        List<Node> typeAliasNodes = rootElement.selectNodes("//" + TYPE_ALIAS);
        typeAliasNodes.forEach(node -> {
            if (node == null || !TYPE_ALIAS.equals(node.getName())) {
                return;
            }
            String alias = ((Element) node).attributeValue("alias");
            String type = ((Element) node).attributeValue("type");
            if (configuration.containTypeAlias(alias)) {
                String msg = String.format("TypeAlias定义重复，别名：“%s”，请确认。", alias);
                LogUtils.error(msg);
                throw new MyBatisException(msg);
            }
            if (StringUtils.isBlank(type)) {
                String msg = String.format("TypeAlias定义无效，别名：“%s”，请确认。", alias);
                LogUtils.error(msg);
                throw new MyBatisException(msg);
            }
            Class<?> typeClass = null;
            try {
                typeClass = Class.forName(type);
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
            if (Objects.isNull(typeClass)) {
                String msg = String.format("TypeAlias定义不存在，别名：“%s”，请确认。", alias);
                LogUtils.error(msg);
                throw new MyBatisException(msg);
            }
            configuration.addTypeAlias(alias, typeClass);
        });
    }

    /**
     * 解析类型处理器节点的方法
     *
     * @param rootElement 配置文件根节点对象
     */
    private void parseTypeHandlers(Element rootElement) {
        List<Node> typeHandlerNodes = rootElement.selectNodes("//" + TYPE_HANDLER);
        typeHandlerNodes.forEach(node -> {
            if (node == null || !TYPE_HANDLER.equals(node.getName())) {
                return;
            }
            String handler = ((Element) node).attributeValue("handler");
            String jdbcType = ((Element) node).attributeValue("jdbcType");
            String javaType = ((Element) node).attributeValue("javaType");

            if (configuration.containTypeHandler(handler)) {
                String msg = String.format("TypeHandler 处理器定义重复，处理器类：“%s”，请确认。", handler);
                LogUtils.error(msg);
                throw new MyBatisException(msg);
            }
            if (StringUtils.isBlank(javaType)) {
                String msg = String.format("TypeHandler 处理器映射定义无效，映射类名：“%s”，请确认。", javaType);
                LogUtils.error(msg);
                throw new MyBatisException(msg);
            }
            Class<?> handlerClass = null;
            Class<?> javaClass = null;
            try {
                handlerClass = Class.forName(handler);
                javaClass = Class.forName(javaType);
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
            if (Objects.isNull(handlerClass)) {
                String msg = String.format("TypeHandler 处理器不存在，处理器类：“%s”，请确认。", handler);
                LogUtils.error(msg);
                throw new MyBatisException(msg);
            }
            if (Objects.isNull(javaClass)) {
                String msg = String.format("TypeHandler 处理器映射类不存在，映射类名：“%s”，请确认。", javaType);
                LogUtils.error(msg);
                throw new MyBatisException(msg);
            }
            TypeHandler typeHandler = new TypeHandler();
            typeHandler.setHandlerKey(handler);
            typeHandler.setHandler(handlerClass);
            typeHandler.setJavaTypeKey(javaType);
            typeHandler.setJavaType(javaClass);
            typeHandler.setJdbcType(jdbcType);
            configuration.addTypeHandler(handler, typeHandler);
        });
    }

    /**
     * 解析数据库关系映射文件节点配置的方法
     *
     * @param rootElement 配置文件根节点对象
     */
    private void parseMappersConfig(Element rootElement) {
        List<Node> mapperNodes = rootElement.selectNodes("//mappers/mapper");
        if (mapperNodes.isEmpty()) {
            return;
        }
        mapperNodes.forEach(node -> {
            String resource = ((Element) node).attributeValue("resource");
            InputStream resourceAsSteam = Resources.getResourceAsSteam(resource);
            XmlMapperBuilder xmlMapperBuilder = new XmlMapperBuilder(configuration);
            xmlMapperBuilder.parse(resourceAsSteam);
        });
    }
}
